//
// TypeDefinition.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;

namespace Mono.Cecil {

	public sealed class TypeDefinition : TypeReference, IGenericContext, IMemberDefinition, ICustomAttributeProvider/*, IHasSecurity */ {

		TypeAttributes attributes;
		TypeReference base_type;
		internal Range fields_range;
		internal Range methods_range;

		short? packing_size;
		int? class_size;

		InterfaceCollection interfaces;
		NestedTypeCollection nested_types;
		MethodDefinitionCollection methods;
		FieldDefinitionCollection fields;
		EventDefinitionCollection events;
		PropertyDefinitionCollection properties;
		CustomAttributeCollection custom_attributes;
		/*SecurityDeclarationCollection m_secDecls;*/

		public TypeAttributes Attributes {
			get { return attributes; }
			set { attributes = value; }
		}

		public TypeReference BaseType {
			get { return base_type; }
			set { base_type = value; }
		}

		public bool HasLayoutInfo {
			get {
				if (packing_size.HasValue || class_size.HasValue)
					return true;

				if (HasImage) {
					Module.Read (this, (type, reader) => reader.ReadTypeLayout (type));
					return packing_size.HasValue || class_size.HasValue;
				}

				return false;
			}
		}

		public short PackingSize {
			get {
				if (packing_size.HasValue)
					return packing_size.Value;

				if (HasImage) {
					Module.Read (this, (type, reader) => reader.ReadTypeLayout (type));
					return packing_size ?? 0;
				}

				return 0;
			}
			set { packing_size = value; }
		}

		public int ClassSize {
			get {
				if (class_size.HasValue)
					return class_size.Value;

				if (HasImage) {
					Module.Read (this, (type, reader) => reader.ReadTypeLayout (type));
					return class_size ?? 0;
				}

				return 0;
			}
			set { class_size = value; }
		}

		public bool HasInterfaces {
			get {
				if (interfaces != null)
					return interfaces.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasInterfaces (type));

				return false;
			}
		}

		public InterfaceCollection Interfaces {
			get {
				if (interfaces != null)
					return interfaces;

				if (HasImage)
					return interfaces = Module.Read (this, (type, reader) => reader.ReadInterfaces (type));

				return interfaces = new InterfaceCollection ();
			}
		}

		public bool HasNestedTypes {
			get {
				if (nested_types != null)
					return nested_types.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasNestedTypes (type));

				return false;
			}
		}

		public NestedTypeCollection NestedTypes {
			get {
				if (nested_types != null)
					return nested_types;

				if (HasImage)
					return nested_types = Module.Read (this, (type, reader) => reader.ReadNestedTypes (type));

				return nested_types = new NestedTypeCollection (this);
			}
		}

		internal new bool HasImage {
			get { return Module != null && Module.HasImage; }
		}

		public bool HasMethods {
			get {
				if (methods != null)
					return methods.Count > 0;

				if (HasImage)
					return methods_range.Length > 0;

				return false;
			}
		}

		public MethodDefinitionCollection Methods {
			get {
				if (methods != null)
					return methods;

				if (HasImage)
					return methods = Module.Read (this, (type, reader) => reader.ReadMethods (type));

				return methods = new MethodDefinitionCollection (this);
			}
		}

		public bool HasFields {
			get {
				if (fields != null)
					return fields.Count > 0;

				if (HasImage)
					return fields_range.Length > 0;

				return false;
			}
		}

		public FieldDefinitionCollection Fields {
			get {
				if (fields != null)
					return fields;

				if (HasImage)
					return fields = Module.Read (this, (type, reader) => reader.ReadFields (type));

				return fields = new FieldDefinitionCollection (this);
			}
		}

		public bool HasEvents {
			get {
				if (events != null)
					return events.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasEvents (type));

				return false;
			}
		}

		public EventDefinitionCollection Events {
			get {
				if (events != null)
					return events;

				if (HasImage)
					return events = Module.Read (this, (type, reader) => reader.ReadEvents (type));

				return events = new EventDefinitionCollection (this);
			}
		}

		public bool HasProperties {
			get {
				if (properties != null)
					return properties.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasProperties (type));

				return false;
			}
		}

		public PropertyDefinitionCollection Properties {
			get {
				if (properties != null)
					return properties;

				if (HasImage)
					return properties = Module.Read (this, (type, reader) => reader.ReadProperties (type));

				return properties = new PropertyDefinitionCollection (this);
			}
		}

		/*public SecurityDeclarationCollection SecurityDeclarations {
			get {
				if (m_secDecls == null)
					m_secDecls = new SecurityDeclarationCollection (this);

				return m_secDecls;
			}
		}*/

		public bool HasCustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasCustomAttributes (type));

				return false;
			}
		}

		public CustomAttributeCollection CustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes;

				if (HasImage)
					return custom_attributes = Module.Read (this, (type, reader) => reader.ReadCustomAttributes (type));

				return custom_attributes = new CustomAttributeCollection ();
			}
		}

		public override bool HasGenericParameters {
			get {
				if (generic_parameters != null)
					return generic_parameters.Count > 0;

				if (HasImage)
					return Module.Read (this, (type, reader) => reader.HasGenericParameters (type));

				return false;
			}
		}

		public override GenericParameterCollection GenericParameters {
			get {
				if (generic_parameters != null)
					return generic_parameters;

				if (HasImage)
					return generic_parameters = Module.Read (this, (type, reader) => reader.ReadGenericParameters (type));

				return generic_parameters = new GenericParameterCollection ();
			}
		}

		#region TypeAttributes

		public bool IsNotPublic {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NotPublic;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NotPublic);
			}
		}

		public bool IsPublic {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.Public;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.Public);
			}
		}

		public bool IsNestedPublic {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedPublic;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedPublic);
			}
		}

		public bool IsNestedPrivate {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedPrivate;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedPrivate);
			}
		}

		public bool IsNestedFamily {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedFamily;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedFamily);
			}
		}

		public bool IsNestedAssembly {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedAssembly;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedAssembly);
			}
		}

		public bool IsNestedFamilyAndAssembly {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedFamANDAssem;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedFamANDAssem);
			}
		}

		public bool IsNestedFamilyOrAssembly {
			get { return (attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.VisibilityMask;
					attributes |= TypeAttributes.NestedFamORAssem;
				} else
					attributes &= ~(TypeAttributes.VisibilityMask & TypeAttributes.NestedFamORAssem);
			}
		}

		public bool IsAutoLayout {
			get { return (attributes & TypeAttributes.LayoutMask) == TypeAttributes.AutoLayout; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.LayoutMask;
					attributes |= TypeAttributes.AutoLayout;
				} else
					attributes &= ~(TypeAttributes.LayoutMask & TypeAttributes.AutoLayout);
			}
		}

		public bool IsSequentialLayout {
			get { return (attributes & TypeAttributes.LayoutMask) == TypeAttributes.SequentialLayout; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.LayoutMask;
					attributes |= TypeAttributes.SequentialLayout;
				} else
					attributes &= ~(TypeAttributes.LayoutMask & TypeAttributes.SequentialLayout);
			}
		}

		public bool IsExplicitLayout {
			get { return (attributes & TypeAttributes.LayoutMask) == TypeAttributes.ExplicitLayout; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.LayoutMask;
					attributes |= TypeAttributes.ExplicitLayout;
				} else
					attributes &= ~(TypeAttributes.LayoutMask & TypeAttributes.ExplicitLayout);
			}
		}

		public bool IsClass {
			get { return (attributes & TypeAttributes.ClassSemanticMask) == TypeAttributes.Class; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.ClassSemanticMask;
					attributes |= TypeAttributes.Class;
				} else
					attributes &= ~(TypeAttributes.ClassSemanticMask & TypeAttributes.Class);
			}
		}

		public bool IsInterface {
			get { return (attributes & TypeAttributes.ClassSemanticMask) == TypeAttributes.Interface; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.ClassSemanticMask;
					attributes |= TypeAttributes.Interface;
				} else
					attributes &= ~(TypeAttributes.ClassSemanticMask & TypeAttributes.Interface);
			}
		}

		public bool IsAbstract {
			get { return (attributes & TypeAttributes.Abstract) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.Abstract;
				else
					attributes &= ~TypeAttributes.Abstract;
			}
		}

		public bool IsSealed {
			get { return (attributes & TypeAttributes.Sealed) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.Sealed;
				else
					attributes &= ~TypeAttributes.Sealed;
			}
		}

		public bool IsSpecialName {
			get { return (attributes & TypeAttributes.SpecialName) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.SpecialName;
				else
					attributes &= ~TypeAttributes.SpecialName;
			}
		}

		public bool IsImport {
			get { return (attributes & TypeAttributes.Import) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.Import;
				else
					attributes &= ~TypeAttributes.Import;
			}
		}

		public bool IsSerializable {
			get { return (attributes & TypeAttributes.Serializable) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.Serializable;
				else
					attributes &= ~TypeAttributes.Serializable;
			}
		}

		public bool IsAnsiClass {
			get { return (attributes & TypeAttributes.StringFormatMask) == TypeAttributes.AnsiClass; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.StringFormatMask;
					attributes |= TypeAttributes.AnsiClass;
				} else
					attributes &= ~(TypeAttributes.StringFormatMask & TypeAttributes.AnsiClass);
			}
		}

		public bool IsUnicodeClass {
			get { return (attributes & TypeAttributes.StringFormatMask) == TypeAttributes.UnicodeClass; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.StringFormatMask;
					attributes |= TypeAttributes.UnicodeClass;
				} else
					attributes &= ~(TypeAttributes.StringFormatMask & TypeAttributes.UnicodeClass);
			}
		}

		public bool IsAutoClass {
			get { return (attributes & TypeAttributes.StringFormatMask) == TypeAttributes.AutoClass; }
			set {
				if (value) {
					attributes &= ~TypeAttributes.StringFormatMask;
					attributes |= TypeAttributes.AutoClass;
				} else
					attributes &= ~(TypeAttributes.StringFormatMask & TypeAttributes.AutoClass);
			}
		}

		public bool IsBeforeFieldInit {
			get { return (attributes & TypeAttributes.BeforeFieldInit) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.BeforeFieldInit;
				else
					attributes &= ~TypeAttributes.BeforeFieldInit;
			}
		}

		public bool IsRuntimeSpecialName {
			get { return (attributes & TypeAttributes.RTSpecialName) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.RTSpecialName;
				else
					attributes &= ~TypeAttributes.RTSpecialName;
			}
		}

		public bool HasSecurity {
			get { return (attributes & TypeAttributes.HasSecurity) != 0; }
			set {
				if (value)
					attributes |= TypeAttributes.HasSecurity;
				else
					attributes &= ~TypeAttributes.HasSecurity;
			}
		}

		#endregion

		public bool IsEnum {
			get { return base_type != null && base_type.FullName == Constants.Enum; }
		}

		public override bool IsValueType {
			get {
				return base_type != null && (
					this.IsEnum || (base_type.FullName == Constants.ValueType && this.FullName != Constants.Enum));
			}
		}

		public override bool IsDefinition {
			get { return true; }
		}

		public new TypeDefinition DeclaringType {
			get { return (TypeDefinition) base.DeclaringType; }
			set { base.DeclaringType = value; }
		}

		internal TypeDefinition (string name, string ns, TypeAttributes attrs) :
			base (name, ns)
		{
			attributes = attrs;
		}

		public TypeDefinition (string name, string ns, TypeAttributes attributes, TypeReference baseType) :
			this (name, ns, attributes)
		{
			this.BaseType = baseType;
		}

		public override TypeDefinition Resolve ()
		{
			return this;
		}
	}

	static partial class Extensions {

		public static TypeReference GetEnumUnderlyingType (this TypeDefinition self)
		{
			var fields = self.Fields;

			for (int i = 0; i < fields.Count; i++) {
				var field = fields [i];
				if (field.Name == "value__")
					return field.FieldType;
			}

			throw new ArgumentException ();
		}
	}
}
